﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Xml.Serialization;
using zEngine;
using zEngine.GameObjs;
using zEngine.Cameras;

namespace zEngine
{
    public class zRender : DrawableGameComponent
    {
        private SpriteBatch _spriteBatch;
        private Camera _camera;
        
        // zObject and extra information for zRender
        private List<zRenderObject> _objects;

        // Settings
        /// <summary>
        /// False: render all objects, True: only render objects in-sight of the camera
        /// </summary>
        private bool _checkInsight = true;

        private bool _useLODImpostor = false;
        private int _noOfImposterLevel = 3;
        private List<double> _distanceLevels;

        /// <summary>
        /// Initializes a new instance of the zRender class.
        /// </summary>
        public zRender(Game game) : base (game)
        {
            _objects = new List<zRenderObject>();
            _distanceLevels = new List<double>();
        }

        public override void Update(GameTime gameTime)
        {
            if (_checkInsight)
                UpdateInsight();
            if (_useLODImpostor)
                UpdateLOD();
            base.Update(gameTime);
        }

        /// <summary>
        /// Draw the list of object however we apply pre-filter techniques
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            for (int i = 0; i < _objects.Count; i++)
            {
                if (_objects[i].InSight || !_checkInsight)
                {
                    _objects[i].zObject.Draw(gameTime);
                }
            }
            base.Draw(gameTime);
        }

        #region Methods
        /// <summary>
        /// Scan the list of object to check objects in-sight or out-sight of the camera
        /// </summary>
        public void UpdateInsight()
        {
            BoundingFrustum frustum = _camera.Frustum;

            // Check if the objects is in-sight or not
            // Update the zRenderObject
            for (int i = 0; i < _objects.Count; i++)
            {
                if (frustum.Contains(_objects[i].zObject.BoundingBox) != ContainmentType.Disjoint)
                {
                    _objects[i].InSight = true;
                }
                else
                {
                    _objects[i].InSight = false;
                }
            }
        }

        /// <summary>
        /// Scan the list of object and update the LOD value of object
        /// </summary>
        public void UpdateLOD()
        {           
            for (int i = 0; i < _objects.Count; i++)
            {
                // If this object not insight, then don't event bother as we don't render it anyways
                if (!_objects[i].InSight) continue;

                zObject zobj = _objects[i].zObject;
                double distance = (double)Math.Abs((_camera.CameraPosition - zobj.Position).Length());
                // Set the default LOD is the lowest level -> if the distance is even further then in the _distanceLevels list
                // zobj will be use lowest LOD
                zobj.LOD = _noOfImposterLevel - 1;

                for (int j = 0; j < _distanceLevels.Count; j++)
                {
                    // If this distance is less then the distance in the list, the LOD should be at j
                    if (distance < _distanceLevels[j])
                    {
                        zobj.LOD = j;
                        break;
                    }
                }
            }
        }

        public void AddObject(zObject newObj)
        {
            zRenderObject zro = new zRenderObject(newObj);
            _objects.Add(zro);
        }
        #endregion

        #region Properties
        public Camera Camera
        {
            get { return _camera; }
            set { _camera = value; }
        }
        public bool CheckInsight
        {
            get { return _checkInsight; }
            set { _checkInsight = value; }
        }
        public bool UseLODImpostor
        {
            get { return _useLODImpostor; }
            set { _useLODImpostor = value; }
        }
        public int NoOfImposterLevel
        {
            get { return _noOfImposterLevel; }
            set { _noOfImposterLevel = value; }
        }
        public List<double> DistanceLevels
        {
            get { return _distanceLevels; }
            set { _distanceLevels = value; }
        }
        public SpriteBatch SpriteBatch
        {
            get { return _spriteBatch; }
            set { _spriteBatch = value; }
        }
        #endregion
    }

    public class zRenderObject
    {
        public zObject zObject;
        public bool InSight;

        /// <summary>
        /// Initializes a new instance of the zRenderObject class.
        /// </summary>
        /// <param name="zObject"></param>
        public zRenderObject(zObject zobject)
        {
            zObject = zobject;
            InSight = true;
        }
    }

}
